package czh.cocoa.ui;

import java.security.Key;

import czh.cocoa.animation.CAAnimation;
import czh.cocoa.animation.CAAnimationDelegate;
import czh.cocoa.animation.CAAnimationGroup;
import czh.cocoa.animation.CABasicAnimation;
import czh.cocoa.animation.CALayer;
import czh.cocoa.animation.CAMediaTimingFunction;
import czh.cocoa.animation.CATransform3D;
import czh.cocoa.animation.CATransformAction;
import czh.cocoa.foundation.NSArray;
import czh.cocoa.foundation.NSMutableArray;
import czh.cocoa.foundation.NSMutableSet;
import czh.cocoa.foundation.NSNumber;
import czh.cocoa.foundation.NSObject;
import czh.cocoa.foundation.NSSet;
import czh.cocoa.foundation.NSString;
import czh.cocoa.graphics.CGColor;
import czh.cocoa.graphics.CGDrawable;
import czh.cocoa.graphics.CGImage;
import czh.cocoa.graphics.CGPath;
import czh.cocoa.graphics.CGPoint;
import czh.cocoa.graphics.CGRect;
import czh.cocoa.graphics.CGSize;
import czh.cocoa.ui.impl.UIImageView;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.EditText;

import static android.view.MotionEvent.*;
import static czh.cocoa.foundation.NSObject.*;
import static czh.cocoa.animation.CALayer.*;
import static czh.cocoa.ui.UIGestureRecognizer.*;

import static czh.cocoa.ui.UIKeyInput.*;

@SuppressLint("DrawAllocation")
class CocoaView extends View {
	
	private boolean mAnimating;
	
	private boolean mAnimatingEnd;
	
	private CAAnimation mEndAnimation;
	
	private double mTimestamp;
	
	private UIView mTouchView;
	
	private NSMutableArray mGestureRecognizers;
	
	private boolean mGestureRecognized;
	
	private NSMutableArray mDelayedEvents; 
	
	private boolean onTouchEvent;
	
	private UIResponder mFirstResponder;
	
	private InputMethodManager mInputMethodManager;
	
	private CALayer mMainLayer;
		
	public CocoaView(Context ctx) {  
		super(ctx);
		setFocusableInTouchMode(true);
		mDelayedEvents = new NSMutableArray(3); 
		mInputMethodManager = (InputMethodManager)ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
	}
	
	void setMainLayer(CALayer layer) {
		mMainLayer = layer;
	}
	
	
	public void onDraw(Canvas canvas) { 	
		
//		mAnimationing = false;
		
		//UIWindow window = UIApplication.sharedApplication().keyWindow();
		if(mMainLayer != null) {
			mTimestamp = System.currentTimeMillis() / 1000.0;
			
			Rect clipBounds = canvas.getClipBounds();
			
			//NSLog("invalidate bounds: %s", new CGRect(clipBounds)); 
			
			RectF clip_bounds = new RectF(clipBounds);
			renderLayer(mMainLayer, canvas, clip_bounds); 
		}
		
//		if(mAnimationing) {
//			invalidate();
//		}
		
	}
	
	private void renderLayer(CALayer layer, Canvas canvas, RectF clipBounds) {
		
		mAnimating = false;
		mAnimatingEnd = false;
		
		boolean hidden = layer.isHidden();
		if(hidden) {
			return;
		}
		
		final CGRect frame = getFrame(layer);
		
//		
		final Matrix matrix = new Matrix();
		
		CATransformAction transform = getTransformAction(layer);
		if(transform != null) {
			matrix.preTranslate(transform.translate.x, transform.translate.y);
		}
		
		matrix.preTranslate(frame.size.width/2, frame.size.height/2);
		
		if(transform != null) {
			
			matrix.preScale(transform.scale.x, transform.scale.y);  
			matrix.preRotate(transform.rotateZ);
			
			float rx = transform.rotateX;
			if(rx != 0) {
				Camera camera = new Camera();
				camera.rotateX(rx);
				Matrix m = new Matrix();
				camera.getMatrix(m);
				matrix.preConcat(m);
			}
			
			float ry = transform.rotateY;
			if(ry != 0) {
				Camera camera = new Camera();
				camera.rotateY(ry);
				Matrix m = new Matrix();
				camera.getMatrix(m);
				matrix.preConcat(m);
			}
			
		} else {
			
			Matrix matrix2 = layer.transform().getMatrix();
			matrix.preConcat(matrix2);   
		} 
		
		matrix.preTranslate(-frame.size.width/2, -frame.size.height/2); 
		
//				
		RectF display_bounds = frame.toRectF();
		
		//NSLog("1. display bounds: %s", new CGRect(display_bounds));
		
		display_bounds = applyTransform(display_bounds, matrix);
		
		//NSLog("2. display bounds: %s", new CGRect(display_bounds)); 
		
		//boolean ret = clipBounds.intersect(display_bounds); 
		
		boolean ret = canvas.quickReject(frame.toRectF(), Canvas.EdgeType.BW); 
		
		//NSLog("canvas clip bounds: %s", new CGRect(canvas.getClipBounds()));
		
		if (ret) {
			//NSLog("no need to draw when out of bounds: " + canvas.getClipBounds());
			if (mAnimatingEnd) {
				layer.removeAnimation(mEndAnimation);
				mEndAnimation = null;
			}
			return;
		}
		
		//clipBounds.left -= display_bounds.left; //frame.origin.x;
		//clipBounds.top -= display_bounds.top;  //frame.origin.y;
		
		clipBounds.offset(-display_bounds.left, -display_bounds.top);
		
		canvas.save();
		
		canvas.translate(frame.origin.x, frame.origin.y);
		
//		matrix = new Matrix();
//		
//		//matrix.preTranslate(frame.origin.x, frame.origin.y);
//		
//		transform = getTransformAction(layer);
//		if(transform != null) {
//			//canvas.translate(transform.translate.x, transform.translate.y);
//			matrix.preTranslate(transform.translate.x, transform.translate.y);
//		}
//		
//		//canvas.translate(frame.size.width/2, frame.size.height/2);
//		matrix.preTranslate(frame.size.width/2, frame.size.height/2);
//		
//		if(transform != null) {
//			
//			//canvas.scale(transform.scale.x, transform.scale.y);
//			//canvas.rotate(transform.rotateZ);
//			
//			matrix.preScale(transform.scale.x, transform.scale.y);  
//			matrix.preRotate(transform.rotateZ);
//			
//			float rx = transform.rotateX;
//			if(rx != 0) {
//				Camera camera = new Camera();
//				camera.rotateX(rx);
//				Matrix m = new Matrix();
//				camera.getMatrix(m);
//				//canvas.concat(m);
//				matrix.preConcat(m);
//			}
//			
//			float ry = transform.rotateY;
//			if(ry != 0) {
//				Camera camera = new Camera();
//				camera.rotateY(ry);
//				Matrix m = new Matrix();
//				camera.getMatrix(m);
//				//canvas.concat(m);
//				matrix.preConcat(m);
//			}
//			
//		} else {
//			
//			Matrix matrix2 = layer.transform().getMatrix();
//			//canvas.concat(matrix);
//			matrix.preConcat(matrix2);
//		}
//		
//		//canvas.translate(-frame.size.width/2, -frame.size.height/2);
//		matrix.preTranslate(-frame.size.width/2, -frame.size.height/2); 
		
		canvas.concat(matrix);
		
		float opacity = getOpacity(layer);
		if(opacity < 1) {
			int alpha = (int) (255 * opacity);
			canvas.saveLayerAlpha(0, 0, frame.size.width, frame.size.height, 
					alpha , Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
		}
		
		if(mAnimating) {
			//RectF region0 = applyTransform(frame.toRectF(), matrix);
			Rect region = getAbslouteInvalidateRegion(layer, display_bounds);  
			if (mAnimatingEnd) {
				region.union(layer.frame().toRect());
				layer.removeAnimation(mEndAnimation);
				mEndAnimation = null;
			}
			
			//NSLog("animation invalidate region: %s", new CGRect(region)); 
			
			invalidate(region);
		}
		
		CGColor color = layer.backgroundColor();
		if(color != null) {
			Paint paint = new Paint();  
			paint.setColor(color.intValue());
			RectF rect = new RectF(0, 0, frame.size.width, frame.size.height);
			canvas.drawRect(rect, paint); 
		}
		
		if(layer.needsDisplay()) {
			layer.display();
		}
		
		//final RectF rect = new RectF(0, 0, frame.size.width, frame.size.height);
		//final RectF clip_bounds = clipBounds.toRectF(); 
		
		final float corner_radius = layer.cornerRadius();
		final RectF clip_bounds = new RectF(0, 0, frame.size.width, frame.size.height);
		final Path clip_path = clipPathForLayer(clip_bounds, corner_radius);   
		
		float shadow_opacity = layer.shadowOpacity();
		if (shadow_opacity > 0) { 
			Paint paint = new Paint();
			CGSize offset = layer.shadowOffset();
			CGColor shadow_color = new CGColor(layer.shadowColor());
			shadow_color.alpha = shadow_opacity;
			paint.setColor(Color.TRANSPARENT);
			paint.setShadowLayer(layer.shadowRadius(), offset.width, offset.height, shadow_color.intValue());
			canvas.drawPath(clip_path, paint);
		}
		
		CGDrawable contents = layer.contents();
		if(contents != null) {
			CGSize contents_size = contents.size();
			
			if (frame.size.width > 0 && frame.size.height > 0 && 
					contents_size.width > 0 && contents_size.height > 0) {
				
				NSString gravity = layer.contentsGravity();
				
				canvas.save();
				
				if (layer.masksToBounds()) { 
					canvas.clipPath(clip_path); 
				} else {
					canvas.clipRect(clip_bounds);  
				}
				
				if (gravity.isEqual(kCAGravityResize)) {
					
					canvas.scale(frame.size.width / contents_size.width, 
							frame.size.height / contents_size.height);
					
					
				} else if(gravity.isEqual(kCAGravityCenter)) {
					
					canvas.translate((frame.size.width - contents_size.width) / 2, 
							(frame.size.height - contents_size.height) / 2);
					
				} else if(gravity.isEqual(kCAGravityResizeAspect)) {
					
					if (frame.size.width / frame.size.height > contents_size.width / contents_size.height) {
						
						float scale = frame.size.height / contents_size.height;
						canvas.translate((frame.size.width - contents_size.width * scale) / 2, 0); 
						canvas.scale(scale, scale);
						
					} else {
						
						float scale = frame.size.width / contents_size.width;
						canvas.translate(0, (frame.size.height - contents_size.height * scale) / 2); 
						canvas.scale(scale, scale);
						
					}
					
				}
				
				contents.draw(canvas);
				
				canvas.restore();
			
			}  
			 
		}
		
		float border_width = layer.borderWidth();
		if (border_width > 0 && layer.borderColor() != null) {  
			CGColor border_color = layer.borderColor();
			Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
			paint.setStyle(Style.STROKE);
			paint.setStrokeWidth(border_width);
			paint.setColor(border_color.intValue()); 
			canvas.drawPath(clip_path, paint);
		}
		
		CGPoint bounds_origin = getBoundsOrigin(layer);
		
		if (layer.masksToBounds()) { 
			canvas.clipPath(clip_path);
		}
		
		canvas.translate(-bounds_origin.x, -bounds_origin.y); 
		
		NSArray sublayers = layer.sublayers();
		for (int i = 0; i < sublayers.count(); i++) {
			CALayer sublayer = (CALayer) sublayers.objectAtIndex(i);
			RectF clip_bounds2 = new RectF(clipBounds);
			clip_bounds2.offset(bounds_origin.x, bounds_origin.y);
			renderLayer(sublayer, canvas, clip_bounds2);
		}
		
		canvas.restore();
		
		if (opacity < 1) {
			canvas.restore();
		}
		
	}
	
	private RectF applyTransform(RectF rect, Matrix m) {
		if (!m.isIdentity()) { 
			Matrix matrix = new Matrix();
			matrix.preTranslate(rect.left /*+ rect.width()/2*/, rect.top /*+ rect.height()/2*/);
			matrix.preConcat(m);
			matrix.preTranslate(-rect.left /*- rect.width()/2*/, -rect.top /*- rect.height()/2*/);
			matrix.mapRect(rect);
		} 
		return rect;
	}
	
	private Rect getAbslouteInvalidateRegion(CALayer layer, RectF region) {
		CALayer superlayer = layer.superlayer();
		while (superlayer != null) { 
			CGRect frame2 = superlayer.frame();
//			region.left += frame2.origin.x;
//			region.top += frame2.origin.y;
			region.offset(frame2.origin.x, frame2.origin.y);
			superlayer = superlayer.superlayer();
		}
		
		Rect region_i = new Rect();
		region.round(region_i);
		return region_i;
	}
	
	private Path clipPathForLayer(RectF rect, float cornerRadius) {
		
		Path path = new Path();
		
		if (cornerRadius > 0) { 
			path.addRoundRect(rect, cornerRadius, cornerRadius, Path.Direction.CW); 
		} else {
			path.addRect(rect, Path.Direction.CW);
		}
		
		return path;
	}
	
	private float getAnimFloatValue(float srcValue, float fromValue, float toValue, 
			float byValue, float progress) {
		
		if(fromValue != NSNotDefined && toValue != NSNotDefined) {
			
			return fromValue + (toValue - fromValue) * progress;
			
		} else if(fromValue != NSNotDefined && byValue != NSNotDefined) {
			
			return fromValue + byValue * progress;
			
		} else if(toValue != NSNotDefined && byValue != NSNotDefined) {
			
			return toValue + byValue * (1 - progress);
			
		} else if(fromValue != NSNotDefined) {
			
			return fromValue + (srcValue - fromValue) * progress;
			
		} else if(toValue != NSNotDefined) {
			
			return toValue + (srcValue - toValue) * (1 - progress);
			
		} else if(byValue != NSNotDefined) {
			
			return srcValue + byValue * progress;
			
		}
		
		return srcValue;
	}
	
	private float getOpacity(CALayer layer) {
		CAAnimation[] anims = getBasicAnimation(layer, CALayer.ANIM_OPACITY);
		
		if(anims != null) {
			
			CABasicAnimation ba = (CABasicAnimation) anims[0];
			
			NSObject fromValue = ba.fromValue();
			NSObject toValue = ba.toValue();
			NSObject byValue = ba.byValue();
			
			float fromOpacity = fromValue != null ? ((NSNumber)fromValue).floatValue() : NSNotDefined;
			float toOpacity = toValue != null ? ((NSNumber)toValue).floatValue() : NSNotDefined;
			float byOpacity = byValue != null ? ((NSNumber)byValue).floatValue() : NSNotDefined;
			
			float progress = getProgress(anims[1], layer);
			
			mAnimating = true; 
			
			return getAnimFloatValue(layer.opacity(), fromOpacity, toOpacity, byOpacity, progress);
			
		}
		
		return layer.opacity();
	}
	
	private CATransformAction getTransformAction(CALayer layer) {
		
		CAAnimation[] anims = getBasicAnimation(layer, CALayer.ANIM_TRANSFORM);
		
		if(anims != null) {
			
			CABasicAnimation ba = (CABasicAnimation) anims[0];
			
			NSObject fromValue = ba.fromValue();
			NSObject toValue = ba.toValue();
			NSObject byValue = ba.byValue();
			
			CATransformAction notDefined = CATransformAction.NotDefined();
			
			CATransformAction fromTransform = fromValue != null ? 
					                    (CATransformAction)fromValue : notDefined;
			CATransformAction toTransform = toValue != null ? 
					                    (CATransformAction)toValue : notDefined;
			CATransformAction byTransform = byValue != null ? 
				                    	(CATransformAction)byValue : notDefined;
			
			float progress = getProgress(anims[1],layer);
			
			mAnimating = true; 
			
			CATransformAction action = new CATransformAction();
			
			float tx = getAnimFloatValue(0, fromTransform.translate.x, 
					toTransform.translate.x, byTransform.translate.x, progress);
			float ty = getAnimFloatValue(0, fromTransform.translate.y, 
					toTransform.translate.y, byTransform.translate.y, progress);
			
			action.translate = new CGPoint(tx, ty);
			
			float sx = getAnimFloatValue(1, fromTransform.scale.x, 
					toTransform.scale.x, byTransform.scale.x, progress);
			float sy = getAnimFloatValue(1, fromTransform.scale.y, 
					toTransform.scale.y, byTransform.scale.y, progress);
			
			action.scale = new CGPoint(sx, sy);
			
			action.rotateX = getAnimFloatValue(0, fromTransform.rotateX, 
					toTransform.rotateX, byTransform.rotateX, progress);
			
			action.rotateY = getAnimFloatValue(0, fromTransform.rotateY, 
					toTransform.rotateY, byTransform.rotateY, progress);
			
			action.rotateZ = getAnimFloatValue(0, fromTransform.rotateZ, 
					toTransform.rotateZ, byTransform.rotateZ, progress);
						
			return action;
		}
		
		return null;
	}
	
	private CGRect getFrame(CALayer layer) {
			
		CAAnimation[] anims = getBasicAnimation(layer, CALayer.ANIM_FRAME);
		
		CGRect fromFrame = null, toFrame = null, byFrame = null;
		
		if(anims != null) {
			
			CABasicAnimation ba = (CABasicAnimation)anims[0];
			
			fromFrame = (CGRect)ba.fromValue();
			toFrame = (CGRect)ba.toValue();
			byFrame = (CGRect)ba.byValue();
			
		} else {
			
			anims = getBasicAnimation(layer, CALayer.ANIM_BOUNDS);
			
			if (anims != null) {
				
				CABasicAnimation ba = (CABasicAnimation)anims[0];
				
				CGRect fromBounds = (CGRect)ba.fromValue();
				CGRect toBounds = (CGRect)ba.toValue();
				CGRect byBounds = (CGRect)ba.byValue();
				
				if (fromBounds != null) { 
					fromFrame = layer.frame();
					fromFrame.size.width = fromBounds.size.width;
					fromFrame.size.height = fromBounds.size.height;
				}
				
				if (toBounds != null) { 
					toFrame = layer.frame();
					toFrame.size.width = toBounds.size.width;
					toFrame.size.height = toBounds.size.height;
				}
				
				if (byBounds != null) { 
					byFrame = layer.frame();
					byFrame.size.width = byBounds.size.width;
					byFrame.size.height = byBounds.size.height;
				}
				
			} else {
				
				anims = getBasicAnimation(layer, CALayer.ANIM_CENTER);
				
				if(anims != null) {
					
					CABasicAnimation ba = (CABasicAnimation)anims[0];
					
					CGPoint fromPoint = (CGPoint)ba.fromValue();
					if(fromPoint != null) {
						fromFrame = layer.frame();
						fromFrame.origin = fromPoint;
					}
					
					CGPoint toPoint = (CGPoint)ba.toValue();
					if (toPoint != null) {
						toFrame = layer.frame();
						toFrame.origin = toPoint;
					}
					
					CGPoint byPoint = (CGPoint)ba.byValue();
					if (byPoint != null) {
						byFrame = new CGRect();
						byFrame.origin = byPoint;
					}
					
				} else {
					
					return layer.frame();
					
				}
				
			}
			
		} 
		
		float progress = getProgress(anims[1], layer);
		
		mAnimating = true; 
		
		if(fromFrame != null && toFrame != null) {
			
			float x = fromFrame.origin.x + (toFrame.origin.x - fromFrame.origin.x) * progress;
			float y = fromFrame.origin.y + (toFrame.origin.y - fromFrame.origin.y) * progress;
			float width = fromFrame.size.width + (toFrame.size.width - fromFrame.size.width) * progress;
			float height = fromFrame.size.height + (toFrame.size.height - fromFrame.size.height) * progress;
			
			return new CGRect(x, y, width, height);
			
			//return fromOpacity + (toOpacity - fromOpacity) * progress;
		} else if(fromFrame != null && byFrame != null) {
			
			float x = fromFrame.origin.x + byFrame.origin.x * progress;
			float y = fromFrame.origin.y + byFrame.origin.y * progress;
			float width = fromFrame.size.width + byFrame.size.width * progress;
			float height = fromFrame.size.height + byFrame.size.height * progress;
			
			return new CGRect(x, y, width, height);
			
			//return fromOpacity + byOpacity * progress;
			
		} else if(toFrame != null && byFrame != null) {
			
			
			float x = toFrame.origin.x + byFrame.origin.x * (1 - progress);
			float y = toFrame.origin.y + byFrame.origin.y * (1 - progress);
			float width = toFrame.size.width + byFrame.size.width * (1 - progress);
			float height = toFrame.size.height + byFrame.size.height * (1 - progress);
			
			return new CGRect(x, y, width, height);
			
			//return toOpacity + byOpacity * (1 - progress);
			
		} else if(fromFrame != null) {
			
			toFrame = layer.frame();
			
			float x = fromFrame.origin.x + (toFrame.origin.x - fromFrame.origin.x) * progress;
			float y = fromFrame.origin.y + (toFrame.origin.y - fromFrame.origin.y) * progress;
			float width = fromFrame.size.width + (toFrame.size.width - fromFrame.size.width) * progress;
			float height = fromFrame.size.height + (toFrame.size.height - fromFrame.size.height) * progress;
			
			return new CGRect(x, y, width, height);
			
		} else if(toFrame != null) {
			
			fromFrame = layer.frame();
			
			float x = fromFrame.origin.x + (toFrame.origin.x - fromFrame.origin.x) * progress;
			float y = fromFrame.origin.y + (toFrame.origin.y - fromFrame.origin.y) * progress;
			float width = fromFrame.size.width + (toFrame.size.width - fromFrame.size.width) * progress;
			float height = fromFrame.size.height + (toFrame.size.height - fromFrame.size.height) * progress;
			
			return new CGRect(x, y, width, height);
			
		} else if(byFrame != null) {
			
			fromFrame = layer.frame();
			
			float x = fromFrame.origin.x + byFrame.origin.x * progress;
			float y = fromFrame.origin.y + byFrame.origin.y * progress;
			float width = fromFrame.size.width + byFrame.size.width * progress;
			float height = fromFrame.size.height + byFrame.size.height * progress;
			
			return new CGRect(x, y, width, height);
			
		}
		
		return layer.frame();
			
	}
	
	private CGPoint getBoundsOrigin(CALayer layer) {
		
		CAAnimation[] anims = getBasicAnimation(layer, CALayer.ANIM_BOUNDS);
		
		if (anims != null) {
			
			CABasicAnimation ba = (CABasicAnimation)anims[0];
			
			CGRect fromBounds = (CGRect)ba.fromValue();
			CGRect toBounds = (CGRect)ba.toValue();
			CGRect byBounds = (CGRect)ba.byValue();
			
			float progress = getProgress(anims[1], layer);
			
			mAnimating = true; 
			
			if(fromBounds != null && toBounds != null) {
				
				float x = fromBounds.origin.x + (toBounds.origin.x - fromBounds.origin.x) * progress;
				float y = fromBounds.origin.y + (toBounds.origin.y - fromBounds.origin.y) * progress;
				
				return CGPointMake(x, y);
				
			} else if(fromBounds != null && byBounds != null) {
				
				float x = fromBounds.origin.x + byBounds.origin.x * progress;
				float y = fromBounds.origin.y + byBounds.origin.y * progress;
				
				return CGPointMake(x, y);
				
			} else if(toBounds != null && byBounds != null) {
				
				
				float x = toBounds.origin.x + byBounds.origin.x * (1 - progress);
				float y = toBounds.origin.y + byBounds.origin.y * (1 - progress);
				
				return CGPointMake(x, y);
				
			} else if(fromBounds != null) {
				
				toBounds = layer.bounds();
				
				float x = fromBounds.origin.x + (toBounds.origin.x - fromBounds.origin.x) * progress;
				float y = fromBounds.origin.y + (toBounds.origin.y - fromBounds.origin.y) * progress;
				
				return CGPointMake(x, y);
				
			} else if(toBounds != null) {
				
				fromBounds = layer.bounds();
				
				float x = fromBounds.origin.x + (toBounds.origin.x - fromBounds.origin.x) * progress;
				float y = fromBounds.origin.y + (toBounds.origin.y - fromBounds.origin.y) * progress;
				
				return CGPointMake(x, y);
				
			} else if(byBounds != null) {
				
				fromBounds = layer.bounds();
				
				float x = fromBounds.origin.x + byBounds.origin.x * progress;
				float y = fromBounds.origin.y + byBounds.origin.y * progress;
				
				return CGPointMake(x, y);
				
			}
			
		} 
			
		return layer.bounds().origin;
	}

	private float getProgress(CAAnimation anim, CALayer layer) {
		
//		if (!anim.isStart()) { 
//			anim.setStart(true);
//			CAAnimationDelegate delegate = anim.delegate();
//			if (delegate != null) {
//				delegate.animationDidStart(anim);
//			}
//		}
		
		float progress = (float) ((mTimestamp
				- anim.beginTime()) / anim.duration() * anim.speed());

		if(anim.autoreverses()) progress /= 2;
		
		float repeat_count = anim.repeatCount();
		if(repeat_count <= 0) {
			repeat_count = (float) (anim.repeatDuration() / anim.duration());
			if(repeat_count <= 0) {
				repeat_count = 1;
			}
		}
		
		boolean done = false;
		
		if(progress >= repeat_count) {
			progress = repeat_count - (int)repeat_count;
			
			if(progress == 0) {
				progress = 1;
				done = true;
			} 
		
		} else if(progress != 0) {
			
			progress -= (int)progress;
			
		} 
		
		if(anim.autoreverses()) {
			if(progress > 0.5) {
				progress = 1 - (progress - 0.5f) * 2;
			} else {
				progress *= 2;
			}
		}
		
		CAMediaTimingFunction function = anim.timingFunction();
		if (function != null) {
			progress = function.getInterpolation(progress);
		}
		
		anim.setProgress(progress); 
		
		if (done) {
			mAnimatingEnd = true;
			//layer.removeAnimation(anim);
			mEndAnimation = anim;
		} 
		
		return progress;
	}
	
	private CAAnimation[] getBasicAnimation(CALayer layer, NSString property) {
		NSArray keys = layer.animationKeys();
		for (int i = 0; i < keys.count(); i++) {
			NSString key = (NSString)keys.objectAtIndex(i);
			CAAnimation anim = layer.animationForKey(key);
			
			if (anim instanceof CABasicAnimation) {
				CABasicAnimation ba = (CABasicAnimation) anim;
				if(ba.keyPath().isEqual(property)) {
					return new CAAnimation[]{ba, ba};
				}
			} else if (anim instanceof CAAnimationGroup) {
				NSArray animations = ((CAAnimationGroup)anim).animations();
				for (int j = 0; j < animations.count(); j++) {
					CAAnimation a = (CAAnimation) animations.objectAtIndex(j); 
					if (a instanceof CABasicAnimation) {
						CABasicAnimation ba = (CABasicAnimation) a;
						if(ba.keyPath().isEqual(property)) {
							return new CAAnimation[]{ba, anim};
						}
					}
				}
			}
			
		}
		
		return null;
	}
	
	public boolean onTouchEvent(MotionEvent event) { 
		
		onTouchEvent = true;
		
		double timestamp = event.getEventTime() / 1000.0;
		
		UIApplication app = UIApplication.sharedApplication();
		UIWindow window = app.keyWindow();
		
		//if(action != 2) {
//			NSLog("touch event: " + event.getPointerCount() + " " + event.getActionIndex() + 
//					" " + event.getActionMasked());
//			for (int i = 0; i < event.getPointerCount(); i++) {
//				CGPoint point2 = new CGPoint(event.getX(i), event.getY(i));
//				NSLog(i + " touch point: " + point2);
//			}
		//}
		
		int action_index = event.getActionIndex();
		int action_masked = event.getActionMasked();
		
//		if(action_masked != MotionEvent.ACTION_MOVE) {
//			NSLog("action masked: " + action_masked);
//		}
		
		switch (action_masked) {
		case ACTION_DOWN:
		case ACTION_POINTER_DOWN:
		{
			CGPoint point = new CGPoint(event.getX(action_index), event.getY(action_index));
			
			if (mTouchView == null && window != null) {
				mTouchView = window.hitTestWithEvent(point, null);
			}
			
			if(mTouchView != null) {
				UIView view = mTouchView;
				
//				CGPoint p = view.convertPointFromView(point, window);
//				UITouch touch = new UITouch(view, p, UITouch.UITouchPhaseBegan, timestamp);
//				NSSet touches = NSSet.setWithObjects(touch);
				
				NSMutableSet touches = new NSMutableSet();
				for (int i = 0; i < event.getPointerCount(); i++) {
					CGPoint point2 = new CGPoint(event.getX(i), event.getY(i));
					CGPoint p = mTouchView.convertPointFromView(point2, window);
					UITouch touch = new UITouch(mTouchView, p, UITouch.UITouchPhaseBegan, timestamp, i);
					touches.addObject(touch);
				}
				
				UIEvent uievent = new UIEvent(touches);
				
				//if (mGestureRecognizer == null) {
					mGestureRecognizers = getGestureRecognizers(view);
					NSArray recognizers = new NSArray(mGestureRecognizers);
					for (NSObject o : recognizers) {
						UIGestureRecognizer r = (UIGestureRecognizer) o;
						r.touchesBeganWithEvent(touches, uievent); 
						
						switch(r.state()) {
						case UIGestureRecognizerStateBegan:
						case UIGestureRecognizerStateEnded:
							//mGestureRecognizer = r;
							//cancelOtherGestureRecognizers(touches);
							mGestureRecognized = true;
							break;
						case UIGestureRecognizerStateFailed:
							mGestureRecognizers.removeObject(r); 
							break;
						}
					}
					
				//} else {
					
					//mGestureRecognizer.touchesBeganWithEvent(touches, new UIEvent(touches));
					
				//}	
				
				if(!mGestureRecognized &&
						(action_masked == ACTION_DOWN || view.isMultipleTouchEnabled())) {
					if (isBeganTouchEventsDelayed()) {
						mDelayedEvents.addObject(uievent);
					} else {
						//if(!deliverDelayedTouchEvents(uievent)) {   20130219
							view.touchesBeganWithEvent(touches, uievent);
						//}
					}
				}
			}
			
		}
			break;
		case MotionEvent.ACTION_MOVE:
		{
			if(mTouchView != null) {
				NSMutableSet touches = new NSMutableSet();
				UITouch first_touch = null; 
				for (int i = 0; i < event.getPointerCount(); i++) {
					CGPoint point = new CGPoint(event.getX(i), event.getY(i));
					CGPoint p = mTouchView.convertPointFromView(point, window);
					UITouch touch = new UITouch(mTouchView, p, UITouch.UITouchPhaseMoved, timestamp, i);
					touches.addObject(touch);
					if(i == 0) {
						first_touch = touch;
					}
				}
				
				//final boolean recognized = mGestureRecognizer != null;
				
				UIEvent uievent = new UIEvent(touches);
				
				//if (!recognized) {
					
					NSArray recognizers = new NSArray(mGestureRecognizers);
					
					for (NSObject o : recognizers) {
						UIGestureRecognizer r = (UIGestureRecognizer) o;
						r.touchesMovedWithEvent(touches, uievent);
						switch(r.state()) {
						case UIGestureRecognizerStateBegan:
						case UIGestureRecognizerStateChanged:
						case UIGestureRecognizerStateEnded:
							//mGestureRecognizer = r;
							//cancelOtherGestureRecognizers(touches);
							mGestureRecognized = true;
							break;
						case UIGestureRecognizerStateFailed:
						case UIGestureRecognizerStateCancelled:
							mGestureRecognizers.removeObject(r);
							break;
						}
						
//						if (mGestureRecognizer != null) {
//							break;
//						}
					}
					
					
					
//				} else {
//					
//					mGestureRecognizer.touchesMovedWithEvent(touches, uievent);
//					
//				}
				
				//if(!recognized) {
					
					if(mGestureRecognized) {
						
						mDelayedEvents.removeAllObjects();
						
						mTouchView.touchesCancelledWithEvent(touches, uievent);
						
					} else {
						if (isBeganTouchEventsDelayed()) {
							
							mDelayedEvents.addObject(uievent); 
							
						} else if(mDelayedEvents.count() > 0) {
							
							mDelayedEvents.addObject(uievent);
							deliverDelayedTouchEvents();
							
						} else {
							
							//if(!deliverDelayedTouchEvents(uievent)) {
								if(touches.count() == 1 || mTouchView.isMultipleTouchEnabled()) {
									mTouchView.touchesMovedWithEvent(touches, uievent);
								} else {
									NSSet touches2 = NSSet.setWithObjects(first_touch);
									mTouchView.touchesMovedWithEvent(touches2, uievent); 
								}
							//}
							
							
						}
					}
					
				//}  
				
			}
		}
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
		{
			if(mTouchView != null) {
				CGPoint point = new CGPoint(event.getX(action_index), event.getY(action_index));
				CGPoint p = mTouchView.convertPointFromView(point, window);
				UITouch touch = new UITouch(mTouchView, p, UITouch.UITouchPhaseEnded, timestamp, action_index);
				NSSet touches = NSSet.setWithObjects(touch);
				
				//final boolean recognized = mGestureRecognizer != null;
				
				UIEvent uievent = new UIEvent(touches);
				
				//if (!recognized) {
					
					NSArray recognizers = new NSArray(mGestureRecognizers);
					
					for (NSObject o : recognizers) {
						UIGestureRecognizer r = (UIGestureRecognizer) o;
						r.touchesEndedWithEvent(touches, uievent);
						switch(r.state()) {
						case UIGestureRecognizerStateBegan:
						case UIGestureRecognizerStateChanged:
						case UIGestureRecognizerStateEnded:
//							mGestureRecognizer = r;
//							cancelOtherGestureRecognizers(touches);
							mGestureRecognized = true;
							break;
						case UIGestureRecognizerStateFailed:
						case UIGestureRecognizerStateCancelled:
							mGestureRecognizers.removeObject(r);
							break;
						}
//						if (mGestureRecognizer != null) {
//							break;
//						}
					}
					
//				} else {
//					
//					mGestureRecognizer.touchesEndedWithEvent(touches, uievent);
//					
//				}
				
				//if(!recognized) {
					
					if(mGestureRecognized) {
						
						mDelayedEvents.removeAllObjects();
						mTouchView.touchesCancelledWithEvent(touches, uievent);
						
					} else if(isBeganTouchEventsDelayed()) {
						
						mDelayedEvents.addObject(uievent);
						
					} else if(mDelayedEvents.count() > 0) {
						
						mDelayedEvents.addObject(uievent);
						deliverDelayedTouchEvents();
						
					} else {
						
//						if (isBeganTouchEventsDelayed()) {
//							mDelayedEvents.addObject(uievent);
//						} else {
//							if (!deliverDelayedTouchEvents(uievent)) {
								if(action_masked == ACTION_UP || mTouchView.isMultipleTouchEnabled()) {
									mTouchView.touchesEndedWithEvent(touches, uievent);
								}
//							}
//						}
						
						
					}
					
				//}  
				
				if(event.getPointerCount() == 1) {
					mTouchView = null;
					//mGestureRecognizer = null;
					mGestureRecognizers.removeAllObjects();
					//mDelayedEvents.removeAllObjects();
					mGestureRecognized = false;
				}
			}
		}
			break;
		case MotionEvent.ACTION_CANCEL:
		{
			if(mTouchView != null) {
				CGPoint point = new CGPoint(event.getX(action_index), event.getY(action_index));
				CGPoint p = mTouchView.convertPointFromView(point, window);
				UITouch touch = new UITouch(mTouchView, p, UITouch.UITouchPhaseCancelled, timestamp, action_index);
				NSSet touches = NSSet.setWithObjects(touch);
				
				//final boolean recognized = mGestureRecognizer != null;
				
				//if (!recognized) {
					for (NSObject o : mGestureRecognizers) {
						UIGestureRecognizer r = (UIGestureRecognizer) o;
						r.touchesCancelledWithEvent(touches, new UIEvent(touches));
					}
//				} else {
//					mGestureRecognizer.touchesCancelledWithEvent(touches, new UIEvent(touches));
//				}
				
				
				//if(!recognized) {
					mTouchView.touchesCancelledWithEvent(touches, new UIEvent(touches));
				//}
				
				if(event.getPointerCount() == 1) {
					mTouchView = null;
					//mGestureRecognizer = null;
					mGestureRecognizers.removeAllObjects();
					mDelayedEvents.removeAllObjects();
					mGestureRecognized = false;
				}
			}
		}
			break;
		default:
			break;
		}
		
		onTouchEvent = false;
		
		return true;
	}
	
	private NSMutableArray getGestureRecognizers(UIView view) {
		NSMutableArray recognizers = new NSMutableArray(3);
		UIView superview = view;
		while(superview != null) {
			NSArray rs = superview.gestureRecognizers();
			for (int i = rs.count() - 1; i >= 0; i--) {
				recognizers.addObject(rs.objectAtIndex(i)); 
			}
			superview = superview.superview();
		} 
		return recognizers;
	}
	
//	private void cancelOtherGestureRecognizers(NSSet touches) {
//		NSArray recognizers2 = new NSArray(mGestureRecognizers);
//		for (NSObject o2 : recognizers2) {
//			UIGestureRecognizer r2 = (UIGestureRecognizer) o2;
//			if (r2 != mGestureRecognizer) {
//				r2.touchesCancelledWithEvent(touches, new UIEvent(touches)); 
//				mGestureRecognizers.removeObject(r2);
//			}
//		}
//	}
	
	private boolean isBeganTouchEventsDelayed() {
		if (mGestureRecognizers != null) {
			for (NSObject o : mGestureRecognizers) {
				UIGestureRecognizer recognizer = (UIGestureRecognizer)o;
				if (recognizer.delaysTouchesBegan()) {
					return true;
				}
			}
		}
		return false;
	}
	
	void removeGestureRecognizer(UIGestureRecognizer recognizer) {
		mGestureRecognizers.removeObject(recognizer);
		if (!isBeganTouchEventsDelayed() && !onTouchEvent) { 
			deliverDelayedTouchEvents();
		}
	}
	
	private void deliverDelayedTouchEvents() {
		
		if (mDelayedEvents.count() > 0) {
			
			//NSLog("deliver delayed touch events !"); 
			
			NSMutableArray events = new NSMutableArray(mDelayedEvents); 
			
			double begin_time = 0;
			
			for (int i = 0; i < events.count(); i++) {
				final UIEvent e = (UIEvent) events.objectAtIndex(i);
				final NSSet touches = e.allTouches();
				final UITouch touch = (UITouch)touches.anyObject();
				final UIView view = touch.view();
				
				if (i == 0) { 
					begin_time = touch.timestamp();
				}
				
				long delayed =  (long) ((touch.timestamp() - begin_time) * 1000);
				//NSLog("deliver touche event delayed: " + delayed);
				
				postDelayed(new Runnable() {
					@Override
					public void run() {
						switch (touch.phase()) {
						case UITouch.UITouchPhaseBegan:
							view.touchesBeganWithEvent(touches, e);
							break;
						case UITouch.UITouchPhaseMoved:
							view.touchesMovedWithEvent(touches, e);
							break;
						case UITouch.UITouchPhaseEnded:
							view.touchesEndedWithEvent(touches, e);
							break;
						}
					}
				}, delayed);
				
				mDelayedEvents.removeObject(e);
				
			}
			
		}
	}
	
	void setFirstResponder(UIResponder responder) {
		if (mFirstResponder != responder) { 
			UIResponder responder0 = mFirstResponder;
			mFirstResponder = responder;
			if (responder0 != null) { 
				responder0.resignFirstResponder();
			}
			if (responder instanceof UIKeyInput) { 
				mInputMethodManager.restartInput(this);
				mInputMethodManager.showSoftInput(this, 0);
			} else {
				mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 
			} 
		}
	}
	
	UIResponder firstResponder() {
		return mFirstResponder;
	}
	
	void resumeSoftInputIfNeed() {
		postDelayed(new Runnable() {
			@Override
			public void run() {
				if (mFirstResponder instanceof UIKeyInput) { 
					mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 
					mInputMethodManager.showSoftInput(CocoaView.this, 0);
				}
			}
		}, 100);
	}
	
	@Override
	public boolean dispatchKeyEventPreIme(KeyEvent event) {
		if (mFirstResponder instanceof UIKeyInput) {
			int keycode = event.getKeyCode();
			if (keycode == KeyEvent.KEYCODE_BACK) {
				mFirstResponder.resignFirstResponder();
				return true;
			}
		}
		return super.dispatchKeyEventPreIme(event);
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		
		//System.out.println("keyCode: " + keyCode);
		
		if (mFirstResponder instanceof UIKeyInput) {
			UIKeyInput input = (UIKeyInput) mFirstResponder;
			if (keyCode == KeyEvent.KEYCODE_DEL) {
				input.deleteBackward();
				return true;
			} else if(keyCode == KeyEvent.KEYCODE_ENTER) {
				input.insertText(NSTR("\n")); 
			}
		} 
		return super.onKeyDown(keyCode, event);  
	}
	
	public BaseInputConnection onCreateInputConnection(EditorInfo outAttrs){
		if (mFirstResponder instanceof UIKeyInput) { 
			UIKeyInput input = (UIKeyInput) mFirstResponder;
			int type = input.keyboardType();
			switch (type) { 
			case UIKeyboardTypeNumberPad:
				outAttrs.inputType = EditorInfo.TYPE_CLASS_NUMBER;
				break;
			case UIKeyboardTypePhonePad:
				outAttrs.inputType = EditorInfo.TYPE_CLASS_PHONE;
				break;
			case UIKeyboardTypeURL:
				outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_URI;
				break;
			case UIKeyboardTypeEmailAddress:
				outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
				break;
			case UIKeyboardTypeDefault:
			default:
				outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT;
				break;
			}
		}
	    return new CocoaInputConnection(this);
    }
	
	private class CocoaInputConnection extends BaseInputConnection {
	    
		public CocoaInputConnection(View view) {  
			super(view, true); 
		}
	    
		public boolean beginBatchEdit() {
			return true;
		}

		public boolean endBatchEdit() {
			return true;
		}
		
		public boolean setComposingText(CharSequence text, int newCursorPosition) {
			return super.setComposingText(text, newCursorPosition);
		}
		
		public boolean commitText(CharSequence text, int newCursorPosition) {
			if (mFirstResponder instanceof UIKeyInput) {
				UIKeyInput input = (UIKeyInput) mFirstResponder;
				input.insertText(NSTR(text.toString()));   
			}
			
			return super.commitText(text, newCursorPosition);
		}
		
		public boolean performPrivateCommand(String action, Bundle data) {   
	        return true;
	    }
		
		 public boolean sendKeyEvent(KeyEvent event) {
			 return super.sendKeyEvent(event);
		 }
		
	}
	
}
